﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using Pixysoft.Framework.Sockets.Core;

namespace Pixysoft.Framework.Sockets.Codec
{
    /// <summary>
    /// web socket 协议解析器，目前仅支持76协议。
    /// </summary>
    public class WebSocketSessionCodecFactory : ISessionCodecFactory
    {
        public ISessionDecoder CreateDecoder()
        {
            return new WebSocketSessionDecoder();
        }

        public ISessionEncoder CreateEncoder()
        {
            return new WebSocketSessionEncoder();
        }
    }

    /// <summary>
    /// 由于是实例化对象，因此可以直接使用内部变量
    /// </summary>
    class WebSocketSessionDecoder : ISessionDecoder
    {
        WebSocketMessageReceiveQueue queue = new WebSocketMessageReceiveQueue();

        private byte[] ServerKey1 = new byte[] { };
        private byte[] ServerKey2 = new byte[] { };

        public bool DoDecode(ISocketIoSession socketIoSession, byte[] socketBuffer, int recvSize, int availableSize, out List<IProtocolMessage> messages)
        {
            messages = new List<IProtocolMessage>();

            try
            {
                if (!queue.Append(socketBuffer, recvSize, availableSize))
                {
                    return false;
                }

                while (!socketIoSession.IsDisposed && queue.ContainMessages())
                {
                    byte[] messageBytes = queue.NextMessage();

                    string message = Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length);

                    if (!queue.Handshaked)
                    {
                        // handshake

                        //GET /pixysoft HTTP/1.1
                        //Upgrade: WebSocket
                        //Connection: Upgrade
                        //Host: localhost:7999
                        //Origin: http://localhost:8080
                        //Sec-WebSocket-Key1: v 241=5 8|t Z0k Z A 9e  8w9 8!Z
                        //Sec-WebSocket-Key2: 2 9Z 6\3;3i  17E8\1  2 k

                        byte[] last8Bytes = new byte[8];
                        Array.Copy(messageBytes, messageBytes.Length - 8, last8Bytes, 0, 8); // Keep the last 8 bytes of the handshake

                        string[] clientHandshakeLines = message.Split(new string[] { Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries);

                        string subDomain = clientHandshakeLines[0].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1];
                        string host = clientHandshakeLines[3].Substring(clientHandshakeLines[3].IndexOf(":") + 2);
                        string origin = clientHandshakeLines[4].Substring(clientHandshakeLines[4].IndexOf(":") + 2);
                        foreach (string line in clientHandshakeLines)
                        {
                            if (line.ToUpper().Contains("SEC-WEBSOCKET-KEY1:"))
                            {
                                BuildServerPartialKey(1, line.Substring(line.IndexOf(":") + 2));
                            }

                            else if (line.ToUpper().Contains("SEC-WEBSOCKET-KEY2:"))
                            {
                                BuildServerPartialKey(2, line.Substring(line.IndexOf(":") + 2));
                            }
                        }

                        byte[] HandshakeText = Encoding.ASCII.GetBytes(CreateHandshake(origin, "ws://" + host + subDomain));
                        byte[] serverHandshakeResponse = new byte[HandshakeText.Length + 16];
                        byte[] serverKey = BuildServerFullKey(last8Bytes);
                        Array.Copy(HandshakeText, serverHandshakeResponse, HandshakeText.Length);
                        Array.Copy(serverKey, 0, serverHandshakeResponse, HandshakeText.Length, 16);

                        socketIoSession.Send(serverHandshakeResponse);
                        queue.Handshaked = true;
                        return true;
                    }
                    else
                    {
                        Pixysoft.Json.CommonJsonModel model = Pixysoft.Json.MyJson.Instance.DeSerialize(message);
                        ProtocolMessage pmessage = new ProtocolMessage();

                        int protocol = -1;
                        int action = -1;
                        int sn = -1;
                        int status = -1;

                        if (model.GetKeys().Contains("protocol") && int.TryParse(model.GetValue("protocol"), out protocol))
                            pmessage.Protocol = protocol;
                        if (model.GetKeys().Contains("action") && int.TryParse(model.GetValue("action"), out action))
                            pmessage.Action = action;
                        if (model.GetKeys().Contains("sn") && int.TryParse(model.GetValue("sn"), out sn))
                            pmessage.Sn = sn;
                        if (model.GetKeys().Contains("status") && int.TryParse(model.GetValue("status"), out status))
                            pmessage.Status = status;

                        if (model.GetKeys().Contains("body"))
                            pmessage.ByteBody = StringCoder.StringToByte(model.GetValue("body"));

                        messages.Add(pmessage);
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                return false;
            }
        }

        /// <summary>
        /// 创建协议
        /// </summary>
        /// <param name="connectionOrigin"></param>
        /// <param name="serverLocation"></param>
        /// <returns></returns>
        private string CreateHandshake(string connectionOrigin, string serverLocation)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine);
            builder.Append("Upgrade: WebSocket" + Environment.NewLine);
            builder.Append("Connection: Upgrade" + Environment.NewLine);
            builder.Append("Sec-WebSocket-Origin: " + connectionOrigin + Environment.NewLine);
            builder.Append("Sec-WebSocket-Location: " + serverLocation + Environment.NewLine);
            builder.Append(Environment.NewLine);

            return builder.ToString();
        }

        /// <summary>
        /// Builds a partial server key according to the web socket protocol
        /// </summary>
        /// <param name="keyNum"></param>
        /// <param name="clientKey"></param>
        private void BuildServerPartialKey(int keyNum, string clientKey)
        {
            string partialServerKey = "";
            byte[] currentKey;
            int spacesNum = 0;
            char[] keyChars = clientKey.ToCharArray();
            foreach (char currentChar in keyChars)
            {
                if (char.IsDigit(currentChar)) partialServerKey += currentChar;
                if (char.IsWhiteSpace(currentChar)) spacesNum++;
            }
            try
            {
                currentKey = BitConverter.GetBytes((int)(Int64.Parse(partialServerKey) / spacesNum));
                if (BitConverter.IsLittleEndian) Array.Reverse(currentKey); // Big endian encoding

                if (keyNum == 1) ServerKey1 = currentKey;
                else ServerKey2 = currentKey;
            }
            catch
            {
                if (ServerKey1 != null) Array.Clear(ServerKey1, 0, ServerKey1.Length);
                if (ServerKey2 != null) Array.Clear(ServerKey2, 0, ServerKey2.Length);
            }
        }

        /// <summary>
        /// Builds the full key the server will return to the client, according to the web socket protocol
        /// </summary>
        /// <returns></returns>
        private byte[] BuildServerFullKey(byte[] last8Bytes)
        {
            byte[] concatenatedKeys = new byte[16];
            Array.Copy(ServerKey1, 0, concatenatedKeys, 0, 4);
            Array.Copy(ServerKey2, 0, concatenatedKeys, 4, 4);
            Array.Copy(last8Bytes, 0, concatenatedKeys, 8, 8);

            // MD5 Hash
            System.Security.Cryptography.MD5 MD5Service = System.Security.Cryptography.MD5.Create();
            return MD5Service.ComputeHash(concatenatedKeys);
        }
    }

    class WebSocketSessionEncoder : ISessionEncoder
    {
        public bool DoEncode(ISocketIoSession socketIoSession, IProtocolMessage[] messages, out byte[] sendBytes)
        {
            sendBytes = new byte[] { };

            try
            {
                using (BinaryWriter writer = new BinaryWriter(new MemoryStream()))
                {
                    foreach (IProtocolMessage message in messages)
                    {
                        writer.Write(WebSocketMessageReceiveQueue.mark_start);
                        string json = CreateJsonProtocol(message.Protocol, message.Action, message.Sn, message.Status, message.ByteBody);
                        writer.Write(Encoding.UTF8.GetBytes(json));
                        writer.Write(WebSocketMessageReceiveQueue.mark_end);

                        LoggerHelper.Debug("{0}::socket finish send message, protocol:{1}, action:{2}",//
                                socketIoSession.Identifier, message.Protocol, message.Action);
                    }

                    if (writer.BaseStream.Length <= 0)
                    {
                        return true;
                    }

                    sendBytes = (writer.BaseStream as MemoryStream).ToArray();

                    return true;
                }
            }
            catch (Exception ex)
            {
                LoggerHelper.Fatal(ex);

                return false;
            }
        }

        private string CreateJsonProtocol(int protocol, int action, int sn, int status, byte[] body)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("{");
            builder.Append("protocol:").Append(protocol);
            builder.Append(",").Append("action:").Append(action);
            builder.Append(",").Append("sn:").Append(sn);
            builder.Append(",").Append("status:").Append(sn);

            if (body.Length > 0)
            {
                builder.Append(",").Append("body:").Append(StringCoder.ByteToString(body));
            }

            builder.Append("}");
            return builder.ToString();
        }
    }

    class WebSocketMessageReceiveQueue : ISocketMessageReceiveQueue
    {
        public const byte mark_start = 0x0;

        public const byte mark_end = 0xff;

        bool handshaked = false;

        MemoryStream stream = new MemoryStream();//正在接受的buffer

        Queue<byte[]> messages = new Queue<byte[]>();//完成的messages


        public bool Append(byte[] socketBuffer, int recvSize, int availableSize)
        {
            byte[] _rbuffer = new byte[recvSize];

            Buffer.BlockCopy(socketBuffer, 0, _rbuffer, 0, _rbuffer.Length);

            if (!handshaked)
            {
                WriteToStream(_rbuffer, 0, _rbuffer.Length, stream);

                //如果还有后续内容，则继续接收

                if (availableSize > 0)
                {
                    return true;
                }

                //否则表示接受完毕

                byte[] content = stream.ToArray();

                ClearStream();

                messages.Enqueue(content);

                return true;
            }
            else
            {
                // 接收到的信息以00开头，以ff结尾，并且接受的长度包含这2位。

                int startIndex = FindStartIndex(_rbuffer, 0);

                do
                {
                    if (startIndex > _rbuffer.Length - 1)
                    {
                        // find nothing, just return.

                        break;
                    }

                    int endIndex = FindEndIndex(_rbuffer, startIndex);

                    if (endIndex > _rbuffer.Length - 1)
                    {
                        // no end, continue read from socket.

                        WriteToStream(_rbuffer, startIndex, _rbuffer.Length - startIndex, stream);

                        return true;
                    }
                    else
                    {
                        // find message.

                        WriteToStream(_rbuffer, startIndex, endIndex - startIndex, stream);

                        byte[] content = stream.ToArray();

                        ClearStream();

                        messages.Enqueue(content);
                    }

                    startIndex = FindStartIndex(_rbuffer, endIndex + 1);
                }
                while (true);
            }

            return true;
        }


        private int FindStartIndex(byte[] _rbuffer, int startIndex)
        {
            int _startIndex = startIndex;

            if (stream.Length > 0)
            {
                _startIndex = 0;
            }
            else
            {
                if (_startIndex > _rbuffer.Length - 1)
                {
                    return _startIndex;
                }

                while (_rbuffer[_startIndex] == mark_start)
                {
                    _startIndex++;

                    if (_startIndex > _rbuffer.Length - 1)
                        break;
                }
            }

            return _startIndex;
        }

        private int FindEndIndex(byte[] _rbuffer, int startIndex)
        {
            int endIndex = startIndex + 1;

            if (endIndex > _rbuffer.Length - 1)
                return endIndex;

            while (_rbuffer[endIndex] != mark_end)
            {
                endIndex++;

                if (endIndex > _rbuffer.Length - 1)
                    break;
            }
            return endIndex;
        }


        public bool ContainMessages()
        {
            return messages.Count > 0;
        }

        public byte[] NextMessage()
        {
            return messages.Dequeue();
        }

        public void ClearAll()
        {
            this.stream.Close();
            this.stream.Dispose();
            this.stream = new MemoryStream();
            messages.Clear();
        }

        public bool Handshaked
        {
            get { return handshaked; }
            set { handshaked = value; }
        }



        private void WriteToStream(byte[] buffer, int offset, int length, Stream stream)
        {
            BinaryWriter writer = new BinaryWriter(stream);
            {
                writer.Write(buffer, offset, length);
            }
        }

        private void ClearStream()
        {
            this.stream.Close();
            this.stream.Dispose();
            this.stream = new MemoryStream();
        }

    }
}
